package mse.tx.arch.pipe.framework;

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.HashMap;
import java.util.Map;

public class FilterFramework extends Thread {
	
	private int bytesread = 0;
	private int byteswrite = 0;

	public class InputPort extends PipedInputStream {
		private FilterFramework owner;
		public FilterFramework sourceFilter = null;

		public InputPort(FilterFramework owner) {
			super();
			this.owner = owner;
		};

		public InputPort(FilterFramework owner, int pipeSize) {
			super(pipeSize);
			this.owner = owner;
		}
	};

	public class OutputPort extends PipedOutputStream {
		private FilterFramework owner;
		public FilterFramework sinkFilter = null;

		public OutputPort(FilterFramework owner) {
			super();
			this.owner = owner;
		};
	};

	private Map<String, InputPort> inputPorts = new HashMap<String, InputPort>();
	private Map<String, OutputPort> outputPorts = new HashMap<String, OutputPort>();

	public InputPort addInputPortByName(String inputPortName) {
		if (inputPortName == null) {
			return null;
		}
		if (inputPorts.keySet().contains(inputPortName)) {
			return inputPorts.get(inputPortName);
		} else {
			inputPorts.put(inputPortName, new InputPort(this));
		}
		return inputPorts.get(inputPortName);
	}

	public InputPort addInputPortByName(String inputPortName, int pipeSize) {
		if (inputPortName == null) {
			return null;
		}
		if (inputPorts.keySet().contains(inputPortName)) {
			return inputPorts.get(inputPortName);
		} else {
			inputPorts.put(inputPortName, new InputPort(this, pipeSize));
		}
		return inputPorts.get(inputPortName);
	}

	public OutputPort addOutputPortByName(String outputPortName) {
		if (outputPortName == null) {
			return null;
		}
		if (outputPorts.keySet().contains(outputPortName)) {
			return outputPorts.get(outputPortName);
		} else {
			outputPorts.put(outputPortName, new OutputPort(this));
		}
		return outputPorts.get(outputPortName);
	}

	public InputPort getInputPortByName(String inputPortName) {
		InputPort inputPort = inputPorts.get(inputPortName);
		if (inputPort != null && inputPort.sourceFilter == null) {
			return inputPort;
		} else {
			for (InputPort iPort : inputPorts.values()) {
				if (iPort.sourceFilter != null) {
					inputPort = iPort.sourceFilter
							.getInputPortByName(inputPortName);
					if (inputPort != null) {
						return inputPort;
					}
				}
			}
		}
		return null;
	} // getInputPortByName

	public OutputPort getOutputPortByName(String outputPortName) {
		OutputPort outputPort = outputPorts.get(outputPortName);
		if (outputPort != null && outputPort.sinkFilter == null) {
			return outputPort;
		} else {
			for (InputPort iPort : inputPorts.values()) {
				if (iPort.sourceFilter != null) {
					outputPort = iPort.sourceFilter
							.getOutputPortByName(outputPortName);
					if (outputPort != null) {
						return outputPort;
					}
				}
			}
		}
		return null;
	} // getOutputPortByName

	public void closeInputPort(String inputPortName) throws IOException {
		InputPort inputPort = getInputPortByName(inputPortName);
		if (inputPort != null) {
			inputPort.sourceFilter = null;
			inputPort.close();
		}
	} // closeInputPort

	public void closeOutputPort(String outputPortName) throws IOException {
		OutputPort outputPort = getOutputPortByName(outputPortName);
		if (outputPort != null) {
			outputPort.sinkFilter = null;
			outputPort.close();
		}
	}
	
	public void closePorts() {
		for (InputPort inputPort: inputPorts.values()) {
			if (inputPort.sourceFilter != null) {
				inputPort.sourceFilter = null;
				try {
					inputPort.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		for (OutputPort outputPort: outputPorts.values()) {
			if (outputPort.sinkFilter != null) {
				outputPort.sinkFilter = null;
				try {
					outputPort.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		System.out.print("\n" + this.getName()
				+ ":: Filter Exiting; bytes read: " + bytesread
				+ "; bytes write: " + byteswrite + "\n");
	}

	public FilterFramework connect(FilterFramework upstreamFilter, String inputPortName,
			String outputPortName) throws IOException {
		InputPort inputPort = getInputPortByName(inputPortName);
		OutputPort outputPort = upstreamFilter
				.getOutputPortByName(outputPortName);

		if (inputPort != null && outputPort != null) {
			// Connect this filter's input to the upstream pipe's output stream
			inputPort.connect(outputPort);
			inputPort.sourceFilter = outputPort.owner;
			outputPort.sinkFilter = inputPort.owner;
		}

		return this;
	} // connect

	public class EndOfStreamException extends Exception {

		private static final long serialVersionUID = 1L;

		public EndOfStreamException() {
			super();
		}

		public EndOfStreamException(String message) {
			super(message);
		}

	} // class EndOfStreamException

	public byte readFilterInputPort(String inputPortName) throws IOException,
			EndOfStreamException {
		InputPort inputPort = inputPorts.get(inputPortName);
		if (inputPort == null) {
			throw new IOException("input port not found: " + inputPortName);
		}
		
		if (inputPort.sourceFilter == null) {
			throw new IOException("input port not connected: " + inputPortName);
		}
		
		if (inputPort.available() == 0 && !inputPort.sourceFilter.isAlive()) {
			throw new EndOfStreamException("End of input stream reached");
		}

		int datum = -1;
		datum = inputPort.read();
		if (datum == -1) {
			throw new EndOfStreamException("End of input stream reached");
		}
		
		++bytesread;

		return (byte) datum;

	} // readFilterInputPort

	public void writeFilterOutputPort(String outputPortName, byte datum)
			throws IOException {
		OutputPort outputPort = outputPorts.get(outputPortName);
		if (outputPort == null) {
			throw new IOException("output port not found: " + outputPortName);
		}
		
		if (outputPort.sinkFilter == null) {
			throw new IOException("output port not connected: " + outputPortName);
		}

		outputPort.write((int) datum);
		outputPort.flush();
		++byteswrite;

	} // writeFilterOutputPort

	public void run() {
	}
}
